package com.gitee.wsl.struct.generator.sample

import com.gitee.wsl.ext.base.currentTimeMillis
import com.gitee.wsl.ext.string.toBinaryString
import com.gitee.wsl.struct.generator.sample.CardIdGeneratorable.Companion.getTimeStampSecond
import kotlinx.atomicfu.locks.SynchronizedObject
import kotlinx.atomicfu.locks.synchronized
import kotlin.jvm.JvmOverloads



/**
 * 加入店铺编号的卡号生成器，卡号固定为16位，为53bit。格式如下：
 * +======================================================================
 * | 4bit店铺编号校验位 | 30bit时间戳 | 3bit机器编号  | 9bit序号 | 7bit卡号校验位 |
 * +======================================================================
 * 1. 30 bit的秒时间戳支持34年
 * 2. 9 bit序号支持512个序号
 * 3. 3 bit机器编号支持8台负载
 * 即卡号生成最大支持8台负载，每台负载每秒钟可以生成512个卡号。
 * 时间戳、机器编号、序号和校验位的bit位数支持业务自定义，方便业务定制自己的生成器。
 *
 * @author liuzhaoming
 * @date 2017/11/23
 */

class ShopCardIdGenerator : CardIdGeneratorable , SynchronizedObject{
    /**
     * 时间bit数，时间的单位为秒，30 bit位时间可以表示34年
     */
    private var timeBits = 30

    /**
     * 机器编码bit数
     */
    private var machineBits = 3

    /**
     * 每秒序列bit数
     */
    private var sequenceBits = 9

    /**
     * 校验bit位数
     */
    private var validationBits = 7

    /**
     * 上一次时间戳
     */
    private var lastStamp = -1L

    /**
     * 系统编号左移bit数
     */
    private var shopOffset = 0

    /**
     * 序列
     */
    private var sequence = 0L

    /**
     * 机器编号
     */
    private var machineId = 1L

    /**
     * 时间左移bit数
     */
    private var timeOffset = 0

    /**
     * 机器编码左移bit数
     */
    private var machineOffset = 0

    /**
     * 序列左移bit数
     */
    private var sequenceOffset = 0

    /**
     * 最大序列号
     */
    private var maxSequence = 0L

    /**
     * 最大校验码
     */
    private var maxCode = 0

    /**
     * 最大店铺编号校验码
     */
    private var maxShopCode = 0L

    /**
     * 起始时间戳
     */
    private var startTimeStamp = 0L

    /**
     * 开始时间格式，默认"2019-01-01 00:00:00"
     */
    private var startTimeString = "2019-01-01 00:00:00"

    @JvmOverloads
    constructor(machineId: Int = 1) {
        val maxMachineId = (-1 shl machineBits).inv()
        if (machineId > maxMachineId) {
            throw IllegalArgumentException("Machine bits is $machineBits, so the max machine id is $maxMachineId")
        }

        this.machineId = machineId.toLong()
        init()
    }

    /**
     * 全参构造函数，便于业务定制卡号生成器
     *
     * @param timeBits        时间bit数
     * @param machineBits     机器编码bit数
     * @param sequenceBits    每秒序列bit数
     * @param validationBits  校验bit位数
     * @param machineId       机器编号
     * @param startTimeString 开始时间
     */
    constructor(
        timeBits: Int, machineBits: Int, sequenceBits: Int, validationBits: Int, machineId: Int,
        startTimeString: String?,
    ) {
        if (timeBits <= 0 || machineBits <= 0 || sequenceBits <= 0 || validationBits <= 0) {
            throw IllegalArgumentException("The bits should be larger than 0")
        }
        if (timeBits + machineBits + sequenceBits + validationBits != 49) {
            throw IllegalArgumentException("The sum of timeBits and machineBits and sequenceBits and validationBits should be 49")
        }

        val maxMachineId = (-1 shl machineBits).inv()
        if (machineId > maxMachineId) {
            throw IllegalArgumentException("Machine bits is $machineBits, so the max machine id is $maxMachineId")
        }

        this.timeBits = timeBits
        this.machineBits = machineBits
        this.sequenceBits = sequenceBits
        this.validationBits = validationBits
        this.machineId = machineId.toLong()
        if (null != startTimeString) {
            this.startTimeString = startTimeString
        }
        init()
    }

    /**
     * 根据给定的系统编号生成卡号
     *
     * @param shopId 店铺编号
     * @return 16位卡号
     */

    fun generate(shopId: String): Long= synchronized(this) {
        if (shopId.isEmpty()) {
            throw IllegalArgumentException("Shop id cannot be null")
        }

        var curStamp = currentSecond
        if (curStamp < lastStamp) {
            throw IllegalArgumentException("Clock moved backwards. Refusing to generate id")
        }

        if (curStamp == lastStamp) {
            sequence = (sequence + 1) and maxSequence
            if (sequence == 0L) {
                curStamp = nextSecond
            }
        } else {
            sequence = 0L
        }
        lastStamp = curStamp

        return combine(shopId, curStamp - startTimeStamp)
    }

    /**
     * 校验卡号是否合法
     *
     * @param shopId 店铺编号
     * @param id     卡号
     * @return boolean 合法返回true，反之false
     */
    fun validate(shopId: String, id: Long): Boolean {
        if (!validateCode(id)) {
            return false
        }

        val bitString: String = id.toString().toBinaryString()
        val bitLength = bitString.length

        val shopCode: Long = getShopCode(shopId, maxShopCode)
        val shopCodeBitString = bitString.substring(0, bitLength - shopOffset)
        val parseShopCode = shopCodeBitString.toLong(2)
        return shopCode == parseShopCode
    }

    /**
     * 解析卡号
     *
     * @param id 卡号
     * @return 解析结果依次是时间戳、机器编码、序列号
     */
    fun parse(id: Long): Array<Long>? {
        if (!validateCode(id)) {
            return null
        }

        val bitString: String = id.toString().toBinaryString()
        val bitLength = bitString.length
        val timestamp =
            bitString.substring(bitLength - timeOffset - timeBits, bitLength - timeOffset).toLong(2)
        val machineId =
            bitString.substring(bitLength - machineOffset - machineBits, bitLength - machineOffset)
                .toLong(2)
        val sequence = bitString.substring(
            bitLength - sequenceOffset - sequenceBits,
            bitLength - sequenceOffset
        ).toLong(2)
        return arrayOf((timestamp + startTimeStamp) * 1000, machineId, sequence)
    }

    /**
     * 将时间戳、机器编号、序号组合成卡号ID
     *
     * @param shopId    店铺ID
     * @param timestamp 时间戳
     * @return 卡号ID
     */
    protected fun combine(shopId: String, timestamp: Long): Long {
        val shopCode: Long = getShopCode(shopId, maxShopCode)
        val originId = shopCode shl shopOffset or (timestamp shl timeOffset
                ) or (machineId shl machineOffset
                ) or (sequence shl sequenceOffset)

        val validationCode: Int = CardIdGeneratorable.getValidationCode(originId, maxCode)
        return originId + validationCode
    }

    /**
     * 数据初始化
     */
    private fun init() {
        sequenceOffset = validationBits
        machineOffset = sequenceOffset + sequenceBits
        timeOffset = machineOffset + machineBits
        shopOffset = timeOffset + timeBits
        maxSequence = (-1L shl sequenceBits).inv()
        maxCode = (-1 shl validationBits).inv()
        startTimeStamp = getTimeStampSecond(startTimeString)
        maxShopCode = 14L
    }

    /**
     * 校验除店铺编号外的所有字段
     *
     * @param id id
     * @return boolean 合法返回true
     */
    private fun validateCode(id: Long): Boolean {
        if (id > MAX_ID || id < MIN_ID) {
            return false
        }

        return validateCode(id, startTimeStamp, timeBits, timeOffset, validationBits, maxCode)
    }

    private val currentSecond: Long
        /**
         * 获取当前时间戳 单位秒
         *
         * @return 时间戳（秒）
         */
        get() = currentTimeMillis / 1000

    private val nextSecond: Long
        /**
         * 获取下一秒钟
         *
         * @return 时间戳（秒）
         */
        get() {
            var second = currentSecond
            while (second <= lastStamp) {
                //IdUtils.sleep(20)
                second = currentSecond
            }

            return second
        }

    companion object {
        /**
         * 最大ID
         */
        private const val MAX_ID = 9999999999999999L

        /**
         * 最小ID
         */
        private const val MIN_ID = 1000000000000000L

        /**
         * 获取店铺编码
         *
         * @param shopId      店铺ID
         * @param maxShopCode 最大店铺编号校验码
         * @return 店铺编码
         */
        fun getShopCode(shopId: String, maxShopCode: Long): Long {
            val numberShopId = shopId.toLong(16)
            val strNumberShopId = numberShopId.toString()
            val numbers = IntArray(strNumberShopId.length)
            for (i in strNumberShopId.indices) {
                numbers[i] = strNumberShopId[i].digitToInt()
            }
            var i = numbers.size - 1
            while (i >= 0) {
                numbers[i] = numbers[i] shl 1
                numbers[i] = numbers[i] / 10 + numbers[i] % 10
                i -= 2
            }

            var validationCode = 0
            for (number in numbers) {
                validationCode += number
            }
            validationCode *= validationCode
            validationCode %= maxShopCode.toInt()
            return if (validationCode < 2L) validationCode + 2L else validationCode.toLong()
        }
    }
}
